#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>

#define HOST_LEN 15
#define L_BORDER "["
#define R_BORDER "]"
#define LABLE "$"
#define LINE_SIZE 1024
#define ARGV_SIZE 32
#define DELIM " "
#define ENV_DELIM "="
#define IN_DIR 1
#define OUT_DIR 2
#define APP_DIR 4


extern int putenv(char *string);
extern int setenv(const char *name, const char *value, int overwrite); 

bool hostflag = false; // 标记是否添加了主机名
char hostname[HOST_LEN];
char commandline[LINE_SIZE];
int lastcode = 0;
bool dupflag = false; // 标记是否需要重定向
int dupstatus = 0;    // 记录重定向种类
const char *filename = ""; // 记录重定向的目标文件

char* getusername(){
    return getenv("USER");
}

// extern int gethostname(char *name, size_t len);

char* getname() {
    if (!hostflag) {
        hostflag = true;
        FILE* fp = fopen("/etc/hostname", "r");
        if (NULL == fp) {
            perror("fopen");
            exit(errno);
        } 
        fgets(hostname, HOST_LEN, fp);
        int len = strlen(hostname);
        if (hostname[len - 1] == '\n')  hostname[len - 1] = '\0';
        fclose(fp);
    }
    
    // 直接调用系统接口
    // if (!hostflag) {
    //     gethostname(hostname, sizeof(hostname));
    //     hostflag = true;
    // }
    return hostname;
}

char* getpwd(){
    return getenv("PWD");
}

void checkdir(char* cline) {
    for (int i = 0; cline[i]; ++i) {
        if (cline[i] == '>' || cline[i] == '<') {
            if (i - 1 != 0) {
                cline[i - 1] = '\0';
            }
            dupflag = true;
            if (cline[i] == '>') {
                if (cline[i + 1] == '>') {
                    dupstatus = APP_DIR;
                    cline[i] = cline[i + 1] = '\0';
                    ++i;
                }
                else {
                    dupstatus = OUT_DIR;
                    cline[i] = '\0';
                }
            }
            else {
                dupstatus = IN_DIR;
                cline[i] = '\0';
            }
            int j = i + 1;
            while (isblank(cline[j])) ++j;
            filename = &cline[j];
            break;
        }
    }
}

void interact(char* cline, int size) {
    // printf(L_BORDER"%s@%s:%s"R_BORDER""LABEL" ", getusername(), gethostname(), getpwd());
    printf("%s@%s:%s"LABLE" ", getusername(), getname(), getpwd());
    char* s = fgets(cline, size, stdin);
    if (NULL == s){
        perror("fgets");
        return ;
    }
    int tmp = strlen(cline);
    if (cline[tmp - 1] == '\n')  
        cline[tmp - 1] = '\0';
    // printf("%s\n", cline);
    
    // 判断是否需要重定向
    checkdir(cline);
}

int splitstring(char* cline, char* argv[]) {
    int pos = -1; // pos指向argv中新分割出的字符串
    argv[++pos] = strtok(cline, DELIM);
    while (argv[pos]) {
        // 最新分割的字符串不为空，继续分割
        argv[++pos] = strtok(NULL, DELIM);
    }

    // 额外让ls指令显示的时候有颜色
    // 当输入非空字符串时, argv[pos]为空，argv[pos - 1]不为空
    if (argv[0] && strcmp(argv[0], "ls") == 0) // 如果本身输入为空, 此时argv[0]为空, 使用该函数会对空指针解引用, 引起段错误
        argv[pos] = "--color", argv[++pos] = NULL; // argv中分割出的非空字符串有pos个 
//    for (int i = 0; argv[i]; ++i)
//        printf("%s\n", argv[i]);
    return pos;
}

void normalexecute(char* argv[]) {
    pid_t id = fork();
    if (id < 0) {
        printf("%s\n", strerror(errno));
    }
    else if (0 == id){
        // 如果需要重定向, 先重定向
        if (dupflag) {
            if (dupstatus & IN_DIR) {
                int fd = open(filename, O_RDONLY);
                if (fd == -1) {
                    perror("open");
                    lastcode = errno;
                    return ;
                }
                dup2(fd, 0);
            }
            else if (dupstatus & OUT_DIR) {
                int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
                if (fd == -1) {
                    perror("open");
                    lastcode = errno;
                    return ;
                }
                dup2(fd, 1); 
            }
            else {
                int fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666);
                if (fd == -1) {
                    perror("open");
                    lastcode = errno;
                    return ;
                }
                dup2(fd, 1); 
            }
        }
        
        // 进行程序替换
        execvp(argv[0], argv);
        printf("%s\n", strerror(errno));
    }
    else {
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        if (ret < 0)    
            printf("%s\n", strerror(errno));
        else if (ret == id) {
            if (WIFEXITED(status))
                lastcode = WEXITSTATUS(status);
        }
    }
}

bool builtExecute(int argc, char* argv[]) {
    bool ret = false;
    if (2 == argc && strcmp(argv[0], "cd") == 0) {
        ret = true;
        if (-1 == chdir(argv[1])) {
            printf("%s\n", strerror(errno));
        }
        else {
            char buffer[LINE_SIZE] = { '\0' };
            getcwd(buffer, sizeof(buffer));
            sprintf(getenv("OLDPWD"), "%s", getenv("PWD"));
            sprintf(getenv("PWD"), "%s", buffer);
            // setenv("PWD", buffer, 1); // setenv似乎会在堆中开辟一块空间存放新的环境变量值，然后让指针指向新的变量值 
            // printf("PWD addr: %p\n", getenv("PWD"));
            // printf("buffer addr: %p\n", buffer);
            // setenv("OLDPWD", oldpwd, 1);
            // printf("OLD addr: %p\n", getenv("OLDPWD"));
        }    
    }
    else if (2 == argc && strcmp(argv[0], "export") == 0) {
        ret = true;
        char *name = strtok(argv[1], ENV_DELIM);
        char *val = strtok(NULL, ENV_DELIM);
        setenv(name, val, 1);
    }
    else if (2 == argc && strcmp(argv[0], "echo") == 0) {
        ret = true;
        if (strcmp(argv[1], "$?") == 0) {
            printf("%d\n", lastcode);
            lastcode = 0;
        }
        else if (*argv[1] == '$') {
            char* val = getenv(argv[1] + 1);
            if (val) printf("%s\n", val);
        }
        else {
            printf("%s\n", argv[1]);
        }
    }
        

    return ret;
}
int main() {
    char* argv[ARGV_SIZE] = { NULL };
    int argc = 0;
    bool builtFlag = true;
    while (1) {
        dupflag = false;
        // 1. 用户交互
        interact(commandline, sizeof(commandline));
        // 2. 字符串分割
        argc = splitstring(commandline, argv);
        
        // 3. 如果没有命令
        if (0 == argc)   continue;

        // 4. 内建命令
        builtFlag = builtExecute(argc, argv);

        // 5. 普通命令
        if (!builtFlag) normalexecute(argv);
    }
    return 0;
}
